from pandac.PandaModules import *
from direct.interval.MetaInterval import *

from direct.showbase import DirectObject
from direct.task import Task
from direct.actor import Actor

import random, math
import xml.sax

version = '0.0.1'

# local imports
import tables
#import colors
#from gfx import *
from globals import *
from LogWriter import log

gamestring = "Maultasche ", version

respair = (1024, 768)

class Point(Point2):
    def __init__(self, x=0.0, y=0.0):
        Point2.__init__(self, x, y)
    
    def distance(self, pt2):
        '''Returns distance from this point to pt2'''
        return pt2 - self
        
    def distance2(self, pt2):
        '''Returns squared distance from this point to pt2'''
        my_x = (self.x - pt2.x)
        my_y = (self.y - pt2.y)

        return (my_x*my_x) + (my_y*my_y)

    def angleto(self, pt2):
        '''Returns the angle from this point to pt2'''
        my_x = self.x - pt2.x
        my_y = self.y - pt2.y

        # dir = -(180.0/3.14159) * atan2(ship.x-x,ship.y-y)

        return math.atan2(my_x,my_y)


def angleto(pt1, pt2):
        '''Returns the angle from this point to pt2'''
        my_x = pt1[0] - pt2[0]
        my_y = pt1[1] - pt2[1]

        # dir = -(180.0/3.14159) * atan2(ship.x-x,ship.y-y)

        return math.atan2(my_x,my_y)

def vectorTo(pt1, pt2):
    my_x = pt1.x - pt2.x
    my_y = pt1.y - pt2.y

    return ogre.Vector3(my_x, my_y, 0)

def hit(unit1, unit2):
    # NOTE: damage calculation formula
    damage = int(unit1.av - unit2.dv)
    unit2.hp -= damage

class City(NodePath):
    def __init__(_, id, player):
        NodePath.__init__(_, 'city')
        _.id = id
        _.player = player
        _.name = "Hauptstadt"
        _.rank = 0
        _.setTag("pickable", "true")
        _.setTag("city", `_.id`)
        
        _.basemesh = "platform.egg"
        _.keepmesh = "keep.egg"
        
        _.reparentTo(render)
        _.base = loader.loadModel("./models/platform.egg")
        _.base.reparentTo(_)
        _.keep = loader.loadModel("./models/keep.egg")
        _.keep.reparentTo(_)
        
        # start from upper-left, going clockwise
        _.buildings = []
        
        _.supply = 20.0

	cNP = _.base.attachNewNode(CollisionNode('supply'))
        csolid = CollisionSphere(0,0,0,_.supply) 
        if csolid is not None:    
            cNP.node().addSolid(csolid)
        base.cTrav.addCollider(cNP, globalHandler)
        if DEBUG is True:
            cNP.show()

    def build(_, newbuilding):
        # see if we have the money for the building
        # assign it a spot
        _.buildings.append(newbuilding)
        # start building
    
    def update(_, timestep):
        #if a building is under construction, cycle its build timer
        pass

class Building:
    def __init__(_):
        pass
        

#class Unit(Actor.Actor):
class Unit(NodePath):
    guid = -1
    
    def __init__(_, companyid, rendernode, uid, unitname, playa, teamname = "Independents"):
        NodePath.__init__(_, unitname)
        
        # <FIXME> : currently using python modules for designing units.
        # perhaps we should move to XML or something in the future
        template = tables.units[unitname]
        # </FIXME>
        
        _.fcolor = playa.pcolor
        #_.fcolor = (0.6, 0.6, 0.6, 1.0)
        _.bcolor = playa.scolor 
        
        _.name = template["name"]
        _.model = template["mesh"]
        _.hp = template["hp"]
        _.ranged_av = template["ranged_av"]
        _.av = template["melee_av"]
        _.dv = template["dv"]

        # default speed is one square a second... I guess.
        _.basespeed = template["speed"]
        _.attack_delay = template["rof"]
        
        _.attack_recharge = 0
        _.xp = 0
        
        # command-related stuff
        _.destination = None
        _.target = None
        _.action = None
            
        #Scene = newScene;//Singleton<SceneManager>::getSingletonPtr();
        _.guid = uid
        _.cid = companyid
        _.setTag('company', `_.cid`)
        #_.uid = uid_counter
        #uid_counter += 1
        
        # use my team's material (color scheme)
        #_.ent.setMaterialName("Teams/" + teamname)
        #_.renderNode = rendernode
        _.reparentTo(render)

        # body generation (physics)

        # set up objects at 0,0,0 by default
        
        mMaxSpeed = 9999.0
        # entity generation (graphics)
        _.actor = Actor.Actor(_.model)
        _.actor.reparentTo(_)
        _.actor.setH(180)
        _.actor.setColor(_.fcolor[0],_.fcolor[1],_.fcolor[2],1.0)
        #_.modelNP = loader.loadModel(_.model)
        #_.modelNP.reparentTo(_)
        _.setPos(0,0,0)
        
        _.moveSequence = Sequence()

    def copy(_):
        return Unit.__init__(_)

    def loadFromXML(_, xmlfile, unitname):
        parser = xml.sax.make_parser()

        # not interested in XML namespaces
        parser.setFeature(xml.sax.handler.feature_namespaces, 0)

        myhandler = XMLUnitHandler(unitname)

        parser.setContentHandler(myhandler)

        parser.parse(xmlfile)
        

    def update(_, frametime):
        ''' functions to be run every cycle '''
        # Hmm... let's see if I'm dead or not
        if _.hp <= 0:
            _.die() # yup! ARGH!
            return False
        
        # count down the delay between attacks
        if _.attack_recharge > 0:
            _.attack_recharge -= _.attack_delay * frametime
            
            # if we just subtracted it into negatives, fix it
            if _.attack_recharge < 0:
                _.attack_recharge = 0

##        # figure out where I'm going
##        if _.destination is not None:
##            _.goTo(frametime, _.destination)
##
##            # nullify the destination when we get there
##            if _.destination == _.getPos():
##                _.destination = None


        # figure out what I'm doing
        if _.target is not None:
            if _.action is "go":
                _.goTo(frametime, _.target)

                # nullify the destination when we get there
                if _.target == _.getPos():
                    _.target = None
            
            elif _.action is "attack":
                if distance(_.getPos(), _.target.getPos()) <= 1:
                    _.attack(_.target)
                else:
                    _.goTo(frametime, _.target.getPos())


    def die(_):
        # first of all, make sure HP is zero
        _.hp = 0
        del _

    def attack(_, enemyunit):
        if _.attack_recharge <= 0:
            # FIXME : replace this with real range
            if distance(_.getPos(), enemyunit.getPos()) <= 1:
                _.attack_recharge = _.attack_delay
                # FIXME: replace this later with something OO 
                hit(_, enemyunit)

    def calcRealSpeed(_):
        # take the base speed and apply modifiers...
        realspeed = _.basespeed

        # terrain
        # formation
        # special effects (charging, retreating, etc)
        
        return realspeed

    def goTo(_, destination):
        _.moveSequence.pause()
        
        _.destination = destination
        #_.heading = angleto(_.pos, _.destination)
        # set the new heading in world space
        #_.node.setDirection(vectorTo(_.pos, _.destination),
        #                    ogre.SceneNode.TS_WORLD)
        dist = _.destination - _.getPos()
        eta = dist.length() / _.basespeed
        
##        dest = NodePath
        
        myInterval= _.posInterval(eta,
            _.destination)#, startPos=_.pickedObj.getPos())
        _.lookAt(_.destination)
        #rotInterval= _.hprInterval(3.0,
        #    dist)
        
        _.moveSequence = Sequence(myInterval)
        #mySequence = Sequence(rotInterval, myInterval)
        _.moveSequence.start()
        
        #rotSequence = Sequence(rotInterval)
        #rotSequence.start()
        

class Officer(Unit):
    pass

class Formation:
    def __init__(_):
        _.front = []
        _.flank = []

        _.num_units = 7
        _.spacing = 3.0

combatFormation = Formation()
combatFormation.front = (
    (-0.75, 0.75),
    (-0.25, 0.75),
    (0.25, 0.75),
    (0.75, 0.75)
)
combatFormation.flank = (
    (-0.75, 0),
    ( 0.75, 0)
)


skirmishFormation = Formation()
skirmishFormation.front = (
    (-0.5, 0.5),
    (-0.5,-0.5),
    (0.5, 0.5),
    (0.5,-0.5)
)
skirmishFormation.flank = (
    (-1.0, 0),
    ( 1.0, 0)
)


class Company:
    def __init__(_,id, player, front, flank, officer):
        _.id = id
        _.player = player
        
        _.formation = combatFormation
        
        _.front = [Unit(_.id, render, 0, front, player),
                    Unit(_.id, render, 1, front, player),
                    Unit(_.id, render, 2, front, player),
                    Unit(_.id, render, 3, front, player)]
        _.flank = [Unit(_.id, render, 4, flank, player),
                    Unit(_.id, render, 5, flank, player)]
        _.officer = Unit(_.id, render, 6, officer, player)

        for unit in range(0,4):
            offset = _.formation.front[unit]
            x = _.formation.spacing * offset[0]# * math.sin(0)
            y = _.formation.spacing * offset[1]# * math.cos(0)
            _.front[unit].setPos(Point3(x,y,1))
            _.front[unit].company = _

        for unit in range(0,2):
            offset = _.formation.flank[unit]
            x = _.formation.spacing * offset[0]
            y = _.formation.spacing * offset[1]
            _.flank[unit].setPos(Point3(x,y,1))
            _.flank[unit].company = _

        _.officer.company = _
	_.officer.setPos(0,0,1)
        # make it so the entire company travels only as fast as the
        # slowest unit
        #_.basespeed =

	_.domain = 8.0
        _.in_supply = False
        
        #_.goTo(Point3(-10,0,0), 3.14159)
        cNP = _.officer.attachNewNode(CollisionNode('domain'))
        csolid = CollisionSphere(0,0,0,_.domain) 
        if csolid is not None:    
            cNP.node().addSolid(csolid)
        base.cTrav.addCollider(cNP, globalHandler)
        if DEBUG is True:
            cNP.show()


    def goTo(_, destination, heading=None):
        # if there was no direction given for the formation to face,
        # make one up
        if heading is None:
            vec = (_.officer.getPos() - destination)
            heading = vec.relativeAngleRad(Vec3(0,1,0))

        # figure out where everyone should be standing at the destination
        for unit in range(0,4):
            offset = _.formation.front[unit]
            polar_offset = Point3(offset[0], offset[1],0).length()
            polar_theta = angleto((0,0), offset)
            x = _.formation.spacing * polar_offset * math.sin(heading+polar_theta)
            y = _.formation.spacing * polar_offset * math.cos(heading+polar_theta)
            dx = destination.getX()
            dy = destination.getY()
            _.front[unit].goTo(Point3(x+dx, y+dy, 1))# + destination)
            
        
        for unit in range(0,2):
            offset = _.formation.flank[unit]
            polar_offset = Point3(offset[0], offset[1],0).length()
            polar_theta = angleto((0,0), offset)
            x = _.formation.spacing * polar_offset * math.sin(heading+polar_theta)
            y = _.formation.spacing * polar_offset * math.cos(heading+polar_theta)
            dx = destination.getX()
            dy = destination.getY()
            _.flank[unit].goTo(Point3(x+dx, y+dy, 1))# + destination)
        # issue a series of goTo commands to the constituent units
        
        _.officer.goTo(destination)

    def setTag(_, tagname, tagval):
        ''' Sets tags for all the units in this company '''
        for unit in _.front:
            unit.setTag(tagname, tagval)
            
        for unit in _.flank:
            unit.setTag(tagname, tagval)

        _.officer.setTag(tagname, tagval)
    
    def setPos(_, newpos):
        for unit in range(0,4):
            offset = _.formation.front[unit]
            x = _.formation.spacing * offset[0]# * math.sin(0)
            y = _.formation.spacing * offset[1]# * math.cos(0)
            _.front[unit].setPos(Point3(x+newpos[0],y+newpos[1],1))

        for unit in range(0,2):
            offset = _.formation.flank[unit]
            x = _.formation.spacing * offset[0]
            y = _.formation.spacing * offset[1]
            _.flank[unit].setPos(Point3(x+newpos[0],y+newpos[1],1))
        
        _.officer.setPos(Point3(newpos[0], newpos[1],1))

class Player(object):
    def __init__(_, name, faction, team, pcolor, scolor):
        _.name = name
        
        _.scolor = scolor
        _.pcolor = pcolor
        
        _.team = team

        _.faction = faction
 

class WorldMgr(DirectObject.DirectObject):
    def __init__(_):
        _.companies = []
        _.company_id = 0

	_.accept('domain-into-domain', _.handleDomains)

    def createCompany(_, pos, playa, front, flank, officer):
        newCompany = Company(_.company_id, playa, front, flank, officer)
        #mousePicker.makePickable(newCompany)
        newCompany.setPos(pos)

        _.companies.insert(_.company_id, newCompany)
        _.company_id += 1

        return newCompany

    def handleDomains(_, entry):
        if DEBUG is True:
            print "Domain collision!"
            print entry

        intoNP = entry.getIntoNodePath().getParent()
        fromNP = entry.getFromNodePath().getParent()

        intoID = int(intoNP.getTag('company'))
        fromID = int(fromNP.getTag('company'))

        if not intoID and fromID:
            return False

        try:
            intoCompany = _.companies[intoID]
            fromCompany = _.companies[fromID]
        except IndexError:
            log.write('error', 'Company %d attempted collision with %d but failed' % (intoID, fromID))
	    log.write('error', 'Companies: '+ `_.companies`)
            return False

        if intoCompany.player.team != fromCompany.player.team:
            print "ACHTUNG ACHTUNG "+ `fromID` +" GREIFT "+ `intoID` +" AN!"

class UnitManager:
    def __init__(_, sceneManager):
        _.entities = []
        _.sceneMgr = sceneManager
        _.uid_counter = 0

    def addUnit(_, newunit):
        _.entities.append(newunit)

    def createUnit(_, unitname, team="Independents"):
        newguy = Unit(_.sceneMgr, _.uid_counter, unitname, team)
        _.entities.append(newguy)
        
        _.uid_counter += 1
        return newguy

    def selectUnitAt(_, pos):
        """ Returns None if no unit exists there"""
        for unit in _.entities:
            if unit.getPos() == pos:
                return unit

        return None

    def update(_,timestep):
        # do per-unit updates
        for unit in _.entities:
            # move units
            unit.update(timestep)
            unit.move(timestep)

fullscreen = False
desired_fps = 60.0

# all in radians, by the way, to avoid having to convert
dirNE = 5.497787143782138 # 315
dirN = 4.7123889803846897 # 270
dirNW = 3.9269908169872414 # 225
dirW = 3.1415926535897931 # 180
dirSW = 2.3561944901923448 # 135
dirS = 1.5707963267948966 # 90
dirSE = 0.78539816339744828 # 45
dirE = 0.0

companies = []
